package simpleMesh;

import hdgeom.interfaces.HasInside;
import hdgeom.primitives.AbstractShape;
import hdgeom.primitives.Plane;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.TreeMap;
import java.util.Vector;

import processing.core.PGraphics;
import processing.core.PMatrix3D;
import processing.core.PVector;

public class Mesh extends AbstractShape implements HasInside {
	public ArrayList<PVector> vertices = new ArrayList<PVector>();
	public ArrayList<Face> faces = new ArrayList<Face>();
	public ArrayList<Edge> edges = new ArrayList<Edge>();

	public Mesh() {

	}

	public Mesh(String file) {
		loadFromOBJ(file);
	}

	public void loadFromOBJ(String file) {
		vertices.clear();
		faces.clear();
		DataInputStream in = null;
		BufferedReader br = null;
		try {
			FileInputStream fstream = new FileInputStream(file);
			in = new DataInputStream(fstream);
			br = new BufferedReader(new InputStreamReader(in));
			String strLine = br.readLine();
			while (strLine != null) {
				// System.out.println(strLine);
				String[] comm = strLine.split("#");
				if (comm.length > 0) {
					String meshData = comm[0];
					// System.out.println("here");
					String[] data = meshData.split(" ");
					if (meshData.startsWith("v ")) {
						float x = Float.valueOf(data[1]);
						float y = Float.valueOf(data[2]);
						float z = Float.valueOf(data[3]);
						vertices.add(new PVector(x, y, z));
					} else if (meshData.startsWith("f ")) {
						if (data.length > 5)
							System.out.println("too many vertices in face");
						Face face = new Face();
						ArrayList<PVector> faceVertices = new ArrayList<PVector>();
						for (int i = 1; i < data.length; i++) {
							String[] number = data[i].split("/");
							face.vertices.add(vertices.get(Integer
									.valueOf(number[0]) - 1));
						}
						faces.add(face);
					}
				}
				strLine = br.readLine();
			}
			br.close();
			in.close();
		} catch (Exception e) {// Catch exception if any
			System.out.println("Error: " + e.getMessage());
		}
	}

	public void saveAsOBJ(String file) {
		FileWriter fw;
		try {
			fw = new FileWriter(file);
			BufferedWriter bw = new BufferedWriter(fw);
			TreeMap<PVector, Integer> map = new TreeMap<PVector, Integer>();
			for (int i = 0; i < vertices.size(); i++) {
				PVector v = vertices.get(i);
				map.put(v, i);
				bw.write("v " + v.x + " " + v.y + " " + v.z);
				bw.newLine();
			}
			for (int i = 0; i < faces.size(); i ++) {
				Face f = faces.get(i);
				String fString = "f";
				for (int ii = 0; ii < f.vertices.size(); ii++) {
					PVector v = f.vertices.get(ii);
					fString += " " + (map.get(v) + 1);
				}
				bw.write(fString);
				bw.newLine();
			}
			bw.flush();
			bw.close();
			System.out.println("OBJ exported: " + file);
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
	}

	public void collectEdges() {
		edges.clear();
		boolean[][] edgeCheck = new boolean[vertices.size()][vertices.size()];
		TreeMap<PVector, Integer> map = new TreeMap<PVector, Integer>();
		for (int i = 0; i < vertices.size(); i++) {
			PVector v = vertices.get(i);
			map.put(v, i);
		}
		for (int i = 0; i < faces.size(); i += 3) {
			Face f = faces.get(i);
			for (int ii = 0; ii < f.size(); ii++) {
				PVector v1 = f.get(ii);
				PVector v2 = f.get((ii + 1) % f.size());
				int i1 = map.get(v1);
				int i2 = map.get(v2);
				edgeCheck[i1][i2] = true;
				edgeCheck[i2][i1] = true;
			}
		}

		for (int i = 0; i < edgeCheck.length - 1; i++) {
			for (int j = i + 1; j < edgeCheck.length; j++) {
				if (edgeCheck[i][j]) {
					Edge edge = new Edge();
					edge.n1 = vertices.get(i);
					edge.n2 = vertices.get(j);
					edges.add(edge);
				}
			}
		}
	}

	public void constructCube() {
		faces.clear();
		vertices.clear();
		vertices.add(new PVector(-0.5f, -0.5f, -0.5f));
		vertices.add(new PVector(-0.5f, 0.5f, -0.5f));
		vertices.add(new PVector(0.5f, 0.5f, -0.5f));
		vertices.add(new PVector(0.5f, -0.5f, -0.5f));
		vertices.add(new PVector(-0.5f, -0.5f, 0.5f));
		vertices.add(new PVector(-0.5f, 0.5f, 0.5f));
		vertices.add(new PVector(0.5f, 0.5f, 0.5f));
		vertices.add(new PVector(0.5f, -0.5f, 0.5f));
		faces.add(new Face(vertices.get(3), vertices.get(2), vertices.get(1),
				vertices.get(0)));
		faces.add(new Face(vertices.get(0), vertices.get(1), vertices.get(5),
				vertices.get(4)));
		faces.add(new Face(vertices.get(1), vertices.get(2), vertices.get(6),
				vertices.get(5)));
		faces.add(new Face(vertices.get(2), vertices.get(3), vertices.get(7),
				vertices.get(6)));
		faces.add(new Face(vertices.get(3), vertices.get(0), vertices.get(4),
				vertices.get(7)));
		faces.add(new Face(vertices.get(4), vertices.get(5), vertices.get(6),
				vertices.get(7)));
	}

	public void constructSphere(float radius, int u, int v) {
		faces.clear();
		vertices.clear();
		PVector[][] nodes = new PVector[u][v];
		float dAngle1 = (float) Math.PI / (u + 1);
		float dAngle2 = 2 * (float) Math.PI / v;
		PVector top = new PVector(0, 0, radius);
		PVector bottom = new PVector(0, 0, -radius);
		vertices.add(top);
		vertices.add(bottom);
		for (int i = 1; i < u + 1; i++) {
			float z = radius * (float) Math.cos(dAngle1 * i);
			float r = radius * (float) Math.sin(dAngle1 * i);
			for (int j = 0; j < v; j++) {
				float x = r * (float) Math.cos(dAngle2 * j);
				float y = r * (float) Math.sin(dAngle2 * j);
				nodes[i - 1][j] = new PVector(x, y, z);
				vertices.add(nodes[i - 1][j]);
			}
		}
		for (int i = 0; i < nodes.length - 1; i++) {
			for (int j = 0; j < nodes[i].length; j++) {
				faces.add(new Face(nodes[i][j], nodes[i][(j + 1)
						% nodes[i].length], nodes[i + 1][(j + 1)
						% nodes[i].length], nodes[i + 1][j]));
			}
		}
		for (int i = 0; i < nodes[0].length; i++) {
			faces.add(new Face(nodes[0][i],
					nodes[0][(i + 1) % nodes[0].length], top));
			faces.add(new Face(nodes[nodes.length - 1][i],
					nodes[nodes.length - 1][(i + 1) % nodes[0].length], bottom));
		}
	}

	public void display3D(PGraphics g) {
		for (int i = 0; i < faces.size(); i++) {
			faces.get(i).display3D(g);
		}
	}

	public void display2D(PGraphics g) {
		for (int i = 0; i < faces.size(); i++) {
			faces.get(i).display2D(g);
		}
	}

	public void triangulate() {
		ArrayList<Face> newFaces = new ArrayList<Face>();
		for (int i = 0; i < getNFaces(); i++) {
			Face f = getFace(i);
			if (f.getNNodes() == 3) {
				newFaces.add(f);
			} else if (f.getNNodes() == 4) {
				Face f1 = new Face(f.get(0), f.get(1), f.get(2));
				newFaces.add(f1);
				Face f2 = new Face(f.get(2), f.get(3), f.get(0));
				newFaces.add(f2);
			} else {
				System.out.println("nodes: " + f.getNNodes());
			}
		}
		this.faces = newFaces;
	}

	public float getDistance(float cellX, float cellY, float cellZ) {
		TrianglePVectorPrecalc tri;
		float minD = Float.MAX_VALUE;
		for (int i = 0; i < faces.size(); i++) {
			if (i % 100000 == 0) {
				System.out.println("scanned triangles: " + i + "/"
						+ faces.size());
			}
			Face face = faces.get(i);
			tri = new TrianglePVectorPrecalc(face.get(0), face.get(1),
					face.get(2));
			float cDist = (float) tri.getDistance(cellX, cellY, cellZ);
			if (minD > cDist)
				minD = cDist;
		}
		return minD;
	}

	public Mesh intersectWith(Mesh mesh) {
		ArrayList<TrianglePVectorPrecalc> t1s = new ArrayList<TrianglePVectorPrecalc>();
		ArrayList<TrianglePVectorPrecalc> t2s = new ArrayList<TrianglePVectorPrecalc>();
		for (int i = 0; i < faces.size(); i++) {
			Face f = faces.get(i);
			t1s.add(new TrianglePVectorPrecalc(f.get(0), f.get(1), f.get(2)));
		}
		for (int i = 0; i < mesh.faces.size(); i++) {
			Face f = mesh.faces.get(i);
			t2s.add(new TrianglePVectorPrecalc(f.get(0), f.get(1), f.get(2)));
		}
		for (int i = 0; i < t1s.size(); i++) {
			TrianglePVectorPrecalc tri1 = t1s.get(i);
			for (int j = 0; j < t2s.size(); j++) {
				if (tri1.id == 0) {
					TrianglePVectorPrecalc tri2 = t2s.get(j);
					if (!tri2.isLine(0.001f) && !tri1.isLine(0.001f)) {
						Plane p2 = new Plane(tri2.get(0), tri2.normal());
						if (tri1.isIntersectWithPlane(p2)) {
							if (tri1.isEdgesIntersectWithTriangle(tri2) > 0
									|| tri2.isEdgesIntersectWithTriangle(tri1) > 0) {
								ArrayList<TrianglePVectorPrecalc> tris1 = tri1
										.splitWithPlane(p2);
								if (tris1.size() > 1) {
									t1s.addAll(tris1);
									tri1.id = 1000;
								}
							}
						}
					}
				}
			}
		}
		Mesh newMesh = new Mesh();
		for (int i = 0; i < t1s.size(); i++) {
			Face tri = t1s.get(i);
			if (tri.id == 0) {
				newMesh.faces.add(tri);
			}
		}
		return newMesh;
	}

	public Mesh intersectBoth(Mesh mesh) {
		Mesh m1 = this.intersectWith(mesh);
		Mesh m2 = mesh.intersectWith(this);
		m1.faces.addAll(m2.faces);
		return m1;
	}

	private Face getFace(int i) {
		// TODO Auto-generated method stub
		return faces.get(i);
	}

	public int getNFaces() {
		// TODO Auto-generated method stub
		return faces.size();
	}

	public boolean contains(PVector v) {
		return contains(v.x, v.y, v.z);
	}

	public void laPlace(float factor) {
		TreeMap<PVector, Integer> map = new TreeMap<PVector, Integer>();
		PVector[] movements = new PVector[vertices.size()];
		for (int i = 0; i < movements.length; i++) {
			movements[i] = new PVector();
		}
		int[] nbs = new int[vertices.size()];
		for (int i = 0; i < vertices.size(); i++) {
			PVector v = vertices.get(i);
			map.put(v, i);
		}

		for (int i = 0; i < faces.size(); i += 3) {
			Face f = faces.get(i);
			for (int ii = 0; ii < f.size(); ii++) {
				PVector v0 = f.get(ii);
				PVector v1 = f.get((ii + 1) % f.size());
				PVector v2 = f.get((ii + 2) % f.size());
				int id = map.get(v1);
				nbs[id] += 2;
				movements[id].add(v0);
				movements[id].add(v2);
			}
		}
		for (int i = 0; i < vertices.size(); i++) {
			PVector average = movements[i];
			average.mult(nbs[i]);
			PVector v = vertices.get(i);
			v.x += (average.x - v.x) * factor;
			v.y += (average.y - v.y) * factor;
			v.z += (average.z - v.z) * factor;
		}
	}

	public boolean contains(float x, float y, float z) {
		int nI = 0;
		for (int i = 0; i < faces.size(); i++) {
			Face face = faces.get(i);
			Triangle tri3D = new Triangle(face.getNode(2), face.getNode(1),
					face.getNode(0));
			if (tri3D.intersect2D(x, y, z)) {
				nI++;
			}
		}
		return nI % 2 == 1;
	}

	public boolean containsConvex(float x, float y, float z) {
		for (int i = 0; i < faces.size(); i++) {
			Face face = faces.get(i);
			Plane plane = new Plane(face.getNode(2), face.getNode(1),
					face.getNode(0));
			if (plane.getDistance(x, y, z) < 0.001)
				return false;
		}
		return true;
	}

	public boolean isOnHull(PVector v, float presicion) {
		return isOnHull(v.x, v.y, v.z, presicion);
	}

	public boolean isOnHull(float x, float y, float z, float presicion) {
		int nI = 0;
		for (int i = 0; i < faces.size(); i++) {
			Face face = faces.get(i);
			TrianglePVectorPrecalc tri3D = new TrianglePVectorPrecalc(
					face.getNode(2), face.getNode(1), face.getNode(0));
			if (tri3D.getDistance(x, y, z) < presicion) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean isInside(float x, float y, float z) {
		// TODO Auto-generated method stub
		return contains(x, y, z);
	}

	@Override
	public void setPosition(int i, float x, float y, float z) {
		// TODO Auto-generated method stub
		this.vertices.get(i).set(x, y, z);
	}

	

	@Override
	public int getNumVertices() {
		// TODO Auto-generated method stub
		return vertices.size();
	}

	@Override
	public void getPosition(int i, float[] coords) {
		// TODO Auto-generated method stub
		PVector v = vertices.get(i);
		coords[0] = v.x;
		coords[1] = v.y;
		coords[2] = v.z;
	}

}
